package com.mjk.common.core.redis;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;

import com.mjk.common.core.redis.listener.RedisKeyDelListener;
import com.mjk.common.core.redis.listener.RedisKeyExpirationListener;
import com.mjk.common.core.redis.listener.RedisKeyUpdateListener;
import com.mjk.common.core.redis.listener.annotation.BeeRedisListener;
import com.mjk.common.core.redis.listener.annotation.RedisKeyEvent;
import com.mjk.common.core.redis.listener.bean.RedisEventEntity;
import com.mjk.common.util.AopTargetUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;
import java.util.*;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import static com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS;
import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
/**
 * 该配置，主要是适配redis键乱码问题
* @author mjk  
* @date 2019年2月18日
 */
@Configuration
@ConditionalOnProperty(name="bee.redis",havingValue="true")
@EnableConfigurationProperties({BeeRedisPropertie.class})
public class RedisConfig implements BeanPostProcessor,ApplicationListener<ContextRefreshedEvent>{
	private final Map<RedisKeyEvent,List<RedisEventEntity>> events = new HashMap<>();
	
	@Bean
	@Primary
	public ObjectMapper objectMapper() {
		ObjectMapper objectMapper = new ObjectMapper();
		//objectMapper.registerModule(new JavaTimeModule());
		objectMapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);
		objectMapper.configure(FAIL_ON_EMPTY_BEANS, false);
		objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
		return objectMapper;
	}
	
	@Bean
	public RedisConnectionFactory getConFactory(BeeRedisPropertie properties){		
		RedisStandaloneConfiguration redisStandaloneConfiguration =
                new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(properties.getHost());
        redisStandaloneConfiguration.setDatabase(0);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(properties.getPassWord()));
        redisStandaloneConfiguration.setPort(properties.getPort());
        return new JedisConnectionFactory(redisStandaloneConfiguration);
	}
	
	@Bean
	@Primary
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setConnectionFactory(redisConnectionFactory);

		ObjectMapper objectMapper = new ObjectMapper();
		//objectMapper.registerModule(new JavaTimeModule());
		objectMapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);
		objectMapper.configure(FAIL_ON_EMPTY_BEANS, false);
		objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
		objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

		GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
		redisTemplate.setDefaultSerializer(valueSerializer);
		redisTemplate.setValueSerializer(valueSerializer);
		redisTemplate.setHashValueSerializer(valueSerializer);

		StringRedisSerializer keySerializer = new StringRedisSerializer();
		redisTemplate.setKeySerializer(keySerializer);
		redisTemplate.setHashKeySerializer(keySerializer);
		return redisTemplate;
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Bean("ratelimitInitLua")
    public DefaultRedisScript getInitRedisScript() {
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/ratelimitinit.lua")));
        redisScript.setResultType(Long.class);
        return redisScript;
    }
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Bean("ratelimitLua")
    public DefaultRedisScript getRedisScript() {
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/ratelimit.lua")));
        redisScript.setResultType(Long.class);
        return redisScript;
    }
	
	@SuppressWarnings("rawtypes")
	@Bean
	public RedisKeyExpirationListener getKeyExpireation(RedisConnectionFactory factory){
		RedisMessageListenerContainer messageListenerContainer = new RedisMessageListenerContainer();
		messageListenerContainer.setConnectionFactory(factory);
		messageListenerContainer.afterPropertiesSet();
		messageListenerContainer.start();
		return new RedisKeyExpirationListener(messageListenerContainer);
	}
	
	@SuppressWarnings("rawtypes")
	@Bean
	public RedisKeyDelListener getKeyDel(RedisConnectionFactory factory){
		RedisMessageListenerContainer messageListenerContainer = new RedisMessageListenerContainer();
		messageListenerContainer.setConnectionFactory(factory);
		messageListenerContainer.afterPropertiesSet();
		messageListenerContainer.start();
		return new RedisKeyDelListener(messageListenerContainer);
	}
	@SuppressWarnings("rawtypes")
	@Bean
	public RedisKeyUpdateListener getKeyUpdate(RedisConnectionFactory factory){
		RedisMessageListenerContainer messageListenerContainer = new RedisMessageListenerContainer();
		messageListenerContainer.setConnectionFactory(factory);
		messageListenerContainer.afterPropertiesSet();
		messageListenerContainer.start();
		return new RedisKeyUpdateListener(messageListenerContainer);
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {		
		
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {	
		
		if(null == AopTargetUtils.getTarget(bean)){
			return bean;
		}
		Class<?> clazz = Objects.requireNonNull(AopTargetUtils.getTarget(bean)).getClass();
		Method[] methods = clazz.getDeclaredMethods();
		for (Method method : methods) {
			String name = method.getName();
			try {
				BeeRedisListener sub = method.getAnnotation(BeeRedisListener.class);
				if (sub != null) {
					RedisEventEntity entity = new RedisEventEntity();
					entity.setTarget(AopTargetUtils.getTarget(bean));
					entity.setMethod(method);
					List<RedisEventEntity> entitys = new ArrayList<>();
					if(ObjectUtils.isEmpty(events.get(sub.evnet()))){						
						entitys.add(entity);						
					}else{
						entitys = events.get(sub.evnet());
						entitys.add(entity);						
					}
					events.put(sub.evnet(), entitys);
				}
			} catch (Exception e) {
				throw new BeanInitializationException("Failed to register event subscriber at method " + name
						+ " in class " + bean.getClass().getName(), e);
			}
		}
		return bean;
	}

	
	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		event.getApplicationContext().getBean(RedisKeyExpirationListener.class).setListener(events.get(RedisKeyEvent.EXPIRE));
		event.getApplicationContext().getBean(RedisKeyDelListener.class).setListener(events.get(RedisKeyEvent.DEL));
		event.getApplicationContext().getBean(RedisKeyUpdateListener.class).setListener(events.get(RedisKeyEvent.UPDATE));
	}
	
}
